package com.podcstr.dao;

import com.podcstr.entity.PersistanceObject;
import org.apache.log4j.Logger;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.List;

/**
 * @author azim
 * @since 5/12/13 
 */

public class GenericDaoImpl<E, I> implements GenericDao<E, I> {

    private final Logger log = Logger.getLogger(GenericDaoImpl.class);

    @PersistenceContext(unitName = "jdbc/podcstr")
    private EntityManager em;

    private Class<E> persistentClass;

    @Override
    public EntityManager getEntityManager() {

        return em;
    }

    @Override
    public E add(E entity) {

        log.info(new Date() + " : Adding new " + entity.getClass().getSimpleName() + " to the database.");
        getEntityManager().persist(entity);
        log.info(new Date() + " : New " + entity.getClass().getSimpleName() + " successfully added to the database.");

        return entity;
    }

    @Override
    public E update(E entity) {
        log.info(new Date() + " : Updating " + entity.getClass().getSimpleName() + " to the database.");
        getEntityManager().merge(entity);
        log.info(new Date() + " : " + entity.getClass().getSimpleName() + " successfully updated to the database.");

        return entity;
    }

    @Override
    public void delete(I id) {
        final StringBuffer queryString = new StringBuffer(
                "DELETE FROM ");
        queryString.append(getPersistentClass().getSimpleName());
        queryString.append(" WHERE id =" + id);

        E e = getEntityManager().find(getPersistentClass(), id);
        if (e != null) {

            log.info(new Date() + " :  Deleting " + e.getClass().getSimpleName() + " from the database with id " + id.toString() + ".");
            getEntityManager().createQuery(queryString.toString()).executeUpdate();
            log.info(new Date() + " :  Successfully deleted " + e.getClass().getSimpleName() + " from the database with id " + id.toString() + ".");
        }
    }

    @Override
    public E findById(I id) {

        E entity = getEntityManager().find(getPersistentClass(), id);
        ;
        if (entity != null) {
            log.info(new Date() + " : Returning " + entity.getClass().getSimpleName() + " from the database with id " + id.toString() + ".");
        }

        return entity;
    }

    @Override
    public List<E> findAll() {

        final StringBuffer queryString = new StringBuffer(
                "SELECT o FROM ");
        queryString.append(getPersistentClass().getSimpleName()).append(" o ");
        List<E> es = getEntityManager().createQuery(queryString.toString()).getResultList();
        log.info(new Date() + " : Returning list of " + getPersistentClass().getSimpleName() + " from the database.");

        return es;
    }

    @Override
    public long count() {

        final StringBuffer query = new StringBuffer();
        query.append("SELECT COUNT(o) from ");
        query.append(getPersistentClass().getSimpleName()).append(" o ");

        return (Long) getEntityManager().createQuery(query.toString()).getSingleResult();
    }

    private Class<E> getPersistentClass() {
        if (persistentClass == null) {
            Type type = getClass().getGenericSuperclass();
            loop:
            while (true) {
                if (type instanceof ParameterizedType) {
                    Type[] arguments = ((ParameterizedType) type)
                            .getActualTypeArguments();
                    for (Type argument : arguments) {
                        if (argument instanceof Class
                                && PersistanceObject.class
                                .isAssignableFrom(((Class<E>) argument))) {
                            persistentClass = (Class<E>) argument;
                            break loop;
                        }
                    }
                }
                type = ((Class<E>) type).getGenericSuperclass();
                if (type == Object.class) {
                    throw new RuntimeException(
                            "Could not find a DatabaseObject subclass parametrized type");
                }
            }
        }
        return persistentClass;
    }
}
